// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package contact

import (
	"context"
	"sync"
	"time"

	"github.com/zeebo/errs"
	"go.uber.org/zap"

	"storj.io/common/identity"
	"storj.io/common/pb"
	"storj.io/common/rpc/rpcstatus"
)

var (
	errCheckInIdentity  = errs.Class("check-in identity")
)

const (
	UnstablePortExpired = time.Second * 30
)
type UnstablePort struct {
	Ipport string
	update time.Time
}
// Endpoint implements the contact service Endpoints.
type Endpoint struct {
	pb.DRPCNodeUnimplementedServer
	log     *zap.Logger
	sync.Mutex
	remoteAdressCache map[string]UnstablePort
}

// NewEndpoint returns a new contact service endpoint.
func NewEndpoint(log *zap.Logger) *Endpoint {
	return &Endpoint{
		log:     log,
		remoteAdressCache: make(map[string]UnstablePort),
	}
}

// CheckIn is periodically called by storage nodes to keep the satellite informed of its existence,
// address, and operator information. In return, this satellite keeps the node informed of its
// reachability.
// When a node checks-in with the satellite, the satellite pings the node back to confirm they can
// successfully connect.
func (endpoint *Endpoint) CheckIn(ctx context.Context, req *pb.CheckInRequest, remoteAddr string) (_ *pb.CheckInResponse, err error) {
	defer mon.Task()(&ctx)(&err)
	peerID, err := identity.PeerIdentityFromContext(ctx)
	if err != nil {
		endpoint.log.Info("failed to get node ID from context", zap.String("node address", req.Address), zap.Error(err))
		return nil, rpcstatus.Error(rpcstatus.Unknown, errCheckInIdentity.New("failed to get ID from context: %v", err).Error())
	}
	nodeID := peerID.ID
	//fmt.Printf("checkin  remote address %s \n",remoteAddr)
	//endpoint.log.Info("check in with adress ",zap.String("",nodeID.String()),zap.String("",req.Address),  zap.String("",remoteAddr))

	//save remoteAdrr
	endpoint.Lock()
	endpoint.remoteAdressCache[nodeID.String()] = UnstablePort{
		Ipport: remoteAddr,
		update: time.Now(),
	}
	endpoint.Unlock()
	return &pb.CheckInResponse{
		PingNodeSuccess:     true,
		PingNodeSuccessQuic: true,
		PingErrorMessage:    "",
	}, nil

}


// GetTime returns current timestamp.
func (endpoint *Endpoint) GetTime(ctx context.Context, req *pb.GetTimeRequest) (_ *pb.GetTimeResponse, err error) {
	defer mon.Task()(&ctx)(&err)

	peerID, err := identity.PeerIdentityFromContext(ctx)
	if err != nil {
		endpoint.log.Info("failed to get node ID from context", zap.Error(err))
		return nil, rpcstatus.Error(rpcstatus.Unauthenticated, errCheckInIdentity.New("failed to get ID from context: %v", err).Error())
	}

	currentTimestamp := time.Now().UTC()
	endpoint.log.Debug("get system current time", zap.Stringer("timestamp", currentTimestamp), zap.Stringer("node id", peerID.ID))
	return &pb.GetTimeResponse{
		Timestamp: currentTimestamp,
	}, nil
}

func (endpoint *Endpoint) GetRemoteAdress(node string)string{
	now := time.Now()

	endpoint.Lock()
	defer  endpoint.Unlock()
	 adress , ok := endpoint.remoteAdressCache[node]
	 if !ok{
	 	return ""
	 }
	 if now.Sub(adress.update) > UnstablePortExpired{
		 delete( endpoint.remoteAdressCache, node)
		 return  ""
	 }
	 return adress.Ipport
}
